﻿using System.Runtime.Intrinsics.X86;
using Daemon.Common;
using Daemon.Data.Substructure.Enums;
using Daemon.Model;
using Daemon.Repository.Contract;
using Daemon.Service.Contract;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Daemon.Data.Infrastructure.Auth;
using Daemon.Middleware;
using Daemon.Common.Auth;
using Daemon.Service.Contract.Jwt;
using Daemon.Model.Jwt;
using Daemon.Common.Attribute;
using Daemon.Common.Cache;
using Daemon.Common.Const;
using Daemon.Repository.EF;
using DaemonAPI.Helpers;
using System.ComponentModel.DataAnnotations;
using Daemon.Data.Infrastructure.Configuration;
using Daemon.Common.CaptchaHelp;

namespace DaemonAPI.Controllers
{
	[ApiController]
	[Route("api/authenticate")]
	public class AuthV2Controller : BaseApiController
	{
		private readonly IAuthInfoService _authInfoService;

		private readonly IHttpContextAccessor _httpContextAccessor;

		private readonly IUserSessionRepository _userSessionRepository;

		private readonly IUserAuthorizationService _userAuthorizationService;

		private readonly IJwtAppService _jwtAppService;

		private readonly IClientConfigRepository _clientConfigRepository;

		private readonly IDaemonDistributedCache _cache;

		private readonly IDaemonProductService _daemonProductService;

		private readonly IApplicationRepository _applicationRepository;

		private readonly SessionItemRepository _sessionItemRepository;

		private readonly ClientAuthCache _clientAuthCache;

		private readonly ICaptcha _captcha;

		private readonly IContextInfoProvider _contextInfoProvider;

		private readonly IAdminUserRepository _adminUserRepository;

		private readonly ITokenInfoRepository _tokenInfoRepository;

		public AuthV2Controller(IDaemonDistributedCache cache,
			IClientConfigRepository clientConfigRepository,
			IJwtAppService jwtAppService,
			IAuthInfoService authInfoService,
			IUserAuthorizationService userAuthorizationService,
			IUserSessionRepository userSessionRepository,
			IHttpContextAccessor httpContextAccessor,
			IDaemonProductService daemonProductService,
			IApplicationRepository applicationRepository,
			SessionItemRepository sessionItemRepository,
			ClientAuthCache clientAuthCache,
			IAdminUserRepository adminUserRepository,
			ITokenInfoRepository tokenInfoRepository,
			ICaptcha captcha,
			IContextInfoProvider contextInfoProvider)
		{
			_authInfoService = authInfoService;
			_httpContextAccessor = httpContextAccessor;
			_userSessionRepository = userSessionRepository;
			_userAuthorizationService = userAuthorizationService;
			_jwtAppService = jwtAppService;
			_clientConfigRepository = clientConfigRepository;
			_sessionItemRepository = sessionItemRepository;
			_daemonProductService = daemonProductService;
			_applicationRepository = applicationRepository;
			_contextInfoProvider = contextInfoProvider;
			_adminUserRepository = adminUserRepository;
			_tokenInfoRepository = tokenInfoRepository;
			_clientAuthCache = clientAuthCache;
			_captcha = captcha;
			_cache = cache;
		}

		[AllowAnonymous]
		[HttpGet]
		[Route(nameof(AuthKey))]
		public ResultModel AuthKey()
		{
			var resultStr = string.Empty;
			string authKey = _cache.Get<string>(CacheKeyConst.AUTH_KEY);
			if (!string.IsNullOrEmpty(authKey))
			{
				resultStr = PasswordHelper.Rfc2898Encrypt(authKey, "daemon");
				return new ResultModel(HttpStatusCode.OK, null, new List<string>() { resultStr });
			}

			authKey = _clientConfigRepository.FindAll().FirstOrDefault()?.AuthKey;
			_cache.Set(CacheKeyConst.AUTH_KEY, authKey);
			resultStr = PasswordHelper.Rfc2898Encrypt(authKey, "daemon");
			return new ResultModel(HttpStatusCode.OK, null, new List<string>() { resultStr });
		}

		[Route(nameof(TestLogin))]
		[HttpPost]
		[AllowAnonymous]
		public ResultModel<string> TestLogin()
		{
			var username = "daemon";
			var prefix = "sf";
			var password = "123456";
			var confirmLogged = false;
			username = username.Trim().ToLowerInvariant();
			(BaseAuthUser user, HttpStatusCode state, string message) = _authInfoService.GetLoginUser(prefix, username, password);
			switch (state)
			{
				case HttpStatusCode.NotFound:
					return new ResultModel<string>(HttpStatusCode.NotFound, message);
				case HttpStatusCode.Unauthorized:
					return new ResultModel<string>(HttpStatusCode.Unauthorized, message);
			}

			if (user != null && !MFAHelper.IsEnableEmailAuth(user) && confirmLogged && _sessionItemRepository.CheckUserIsOnline(user.Id))
			{
				return new ResultModel<string>(HttpStatusCode.PreconditionFailed, "Logined");
			}

			if (user != null)
			{
				if (!MFAHelper.IsEnableEmailAuth(user))
				{
					_sessionItemRepository.DeleteByUserId(user.Id, LogoutReasonEnum.LoggedInOnOtherDevice);
				}
			}

			var tokenResult = TokenHelper.CreateToken(username, prefix, user);
			AddLoginInfo(user);
			var authInfo = new AuthInfoEF(JsonConvert.DeserializeObject<BaseAuthUser>(JsonConvert.SerializeObject(_userAuthorizationService.GetUserEntity(username, null))));
			_clientAuthCache.AddClientUserEntryAsync(prefix + "_" + username, authInfo);
			return new ResultModel<string>(HttpStatusCode.OK, "", tokenResult);
		}

		[Route(nameof(Login))]
		[HttpPost]
		[AllowAnonymous]
		public ResultModel<dynamic> Login([Required] string prefix, [FromQuery] string username, [FromBody] string password, bool confirmLogged = false)
		{
			username = username.Trim().ToLowerInvariant();
			(BaseAuthUser user, HttpStatusCode state, string message) = _authInfoService.GetLoginUser(prefix, username, password);
			switch (state)
			{
				case HttpStatusCode.NotFound:
					return new ResultModel<dynamic>(HttpStatusCode.NotFound, message);
				case HttpStatusCode.Unauthorized:
					return new ResultModel<dynamic>(HttpStatusCode.Unauthorized, message);
			}

			if (user != null && !MFAHelper.IsEnableEmailAuth(user) && confirmLogged && _sessionItemRepository.CheckUserIsOnline(user.Id))
			{
				return new ResultModel<dynamic>(HttpStatusCode.PreconditionFailed, "Logined");
			}

			if (user != null)
			{
				if (!MFAHelper.IsEnableEmailAuth(user))
				{
					_sessionItemRepository.DeleteByUserId(user.Id, LogoutReasonEnum.LoggedInOnOtherDevice);
				}
			}

			var tokenResult = TokenHelper.CreateToken(username, prefix, user);
			AddLoginInfo(user);
			var authInfo = new AuthInfoEF(JsonConvert.DeserializeObject<BaseAuthUser>(JsonConvert.SerializeObject(_userAuthorizationService.GetUserEntity(username, null))));
			ServiceLocator.Resolve<ClientAuthCache>().AddClientUserEntryAsync(username, authInfo);
			return new ResultModel<dynamic>(HttpStatusCode.OK, new { Token = tokenResult, LoginType = "account" }, "");
		}

		[Route(nameof(EmailLogin))]
		[HttpPost]
		[AllowAnonymous]
		public ResultModel<dynamic> EmailLogin([Required] string prefix, [FromQuery] string email, [FromBody] string code)
		{
			email = email.Trim().ToLowerInvariant();
			(BaseAuthUser user, HttpStatusCode state, string message) = _authInfoService.GetLoginUserByEmail(prefix, email);
			switch (state)
			{
				case HttpStatusCode.NotFound:
					return new ResultModel<dynamic>(HttpStatusCode.NotFound, message);
				case HttpStatusCode.Unauthorized:
					return new ResultModel<dynamic>(HttpStatusCode.Unauthorized, message);
			}

			if (code != _cache.Get<string>($"{user.UserName}_code"))
			{
				return new ResultModel<dynamic>(HttpStatusCode.Unauthorized, "验证码错误！");
			}

			if (user != null && !MFAHelper.IsEnableEmailAuth(user) && _sessionItemRepository.CheckUserIsOnline(user.Id))
			{
				return new ResultModel<dynamic>(HttpStatusCode.PreconditionFailed, "Logined");
			}

			if (user != null)
			{
				if (!MFAHelper.IsEnableEmailAuth(user))
				{
					_sessionItemRepository.DeleteByUserId(user.Id, LogoutReasonEnum.LoggedInOnOtherDevice);
				}
			}

			var tokenResult = TokenHelper.CreateToken(user.UserName, prefix, user);
			AddLoginInfo(user);
			var authInfo = new AuthInfoEF(JsonConvert.DeserializeObject<BaseAuthUser>(JsonConvert.SerializeObject(_userAuthorizationService.GetUserEntity(user.UserName, null))));
			ServiceLocator.Resolve<ClientAuthCache>().AddClientUserEntryAsync(user.UserName, authInfo);
			return new ResultModel<dynamic>(HttpStatusCode.OK, new { Token = tokenResult, LoginType = "account" }, "");
		}

		[Route(nameof(SendCode))]
		[HttpGet]
		[AllowAnonymous]
		public ResultModel<string> SendCode([Required] string prefix, [FromQuery] string email)
		{
			email = email.Trim().ToLowerInvariant();
			(BaseAuthUser user, HttpStatusCode state, string message) = _authInfoService.GetLoginUserByEmail(prefix, email);
			switch (state)
			{
				case HttpStatusCode.NotFound:
					return new ResultModel<string>(HttpStatusCode.NotFound, message);
				case HttpStatusCode.Unauthorized:
					return new ResultModel<string>(HttpStatusCode.Unauthorized, message);
			}

			var securityCode = MFAHelper.GeneratePin(user.Email, prefix, out bool sendSuccess);
			_cache.Set<string>($"{user.UserName}_code", securityCode, TimeSpan.FromMinutes(1));
			return sendSuccess ? new ResultModel<string>(HttpStatusCode.OK, securityCode, "验证码发送成功！") : new ResultModel<string>(HttpStatusCode.BadRequest, securityCode, "验证码发送失败！");
		}

		[Route(nameof(GenerateCaptchaImage))]
		[HttpGet]
		[AllowAnonymous]
		public async Task<ResultModel<CaptchaResult>> GenerateCaptchaImage()
		{
			var captchaStr = await _captcha.GenerateRandomCaptchaAsync();
			var captchaResult = await _captcha.GenerateCaptchaImageAsync(captchaStr);
			captchaResult.Base64Str = Convert.ToBase64String(captchaResult.CaptchaMemoryStream.ToArray());
			return new ResultModel<CaptchaResult>(HttpStatusCode.OK, captchaResult);
		}


		[Route(nameof(AuthInfo))]
		[HttpGet]
		[AllowAnonymous]
		public ResultModel<IAuthInfo> AuthInfo()
		{
			IAuthInfo authInfo = ServiceLocator.Resolve<ClientAuthCache>().GetAuthInfoByClientUser(UserName);
			_contextInfoProvider.AuthInfo = authInfo;
			return new ResultModel<IAuthInfo>(HttpStatusCode.OK, authInfo, null);
		}

		[Route(nameof(LoginOut))]
		[HttpDelete]
		public ResultModel<bool> LoginOut([FromBody] AuthCondition condition)
		{
			bool clearSessionResult = _sessionItemRepository.Delete(condition.SessionItemId, LogoutReasonEnum.UserInitiatedLogout);
			int userId = _adminUserRepository.FindAll().First(r => r.UserName == condition.UserName).Id;
			_sessionItemRepository.DeleteUserSession(userId, LogoutReasonEnum.UserInitiatedLogout);

			var tokenKey = TokenHelper.GetTokenKey(condition.UserName, condition.Prefix);
			_clientAuthCache.ClearClientUserEntryAsync(condition.UserName);
			var num = _tokenInfoRepository.Delete(tokenKey);
			return new ResultModel<bool>(HttpStatusCode.OK, num > 0 && clearSessionResult);
		}

		/// <summary>
		/// Delete auth.
		/// </summary>
		/// <param name="condition">Delete auth parameters</param>
		/// <returns>No content</returns>
		[Route("")]
		[HttpDelete]
		[SecuredItemAuthorize]
		public ResultModel<bool> Delete([FromBody] AuthCondition condition)
		{
			return DeleteAuth(condition.Prefix, condition.UserName, logoutReaon: LogoutReasonEnum.UserInitiatedLogout);
		}

		private ResultModel<bool> DeleteAuth(string prefix, string username, LogoutReasonEnum logoutReaon = LogoutReasonEnum.Unknown)
		{
			int userId = _adminUserRepository.FindAll().First(r => r.UserName == username).Id;
			_sessionItemRepository.DeleteUserSession(userId, logoutReaon);

			var tokenKey = TokenHelper.GetTokenKey(username, prefix);
			_clientAuthCache.ClearClientUserEntryAsync(username);
			var num = _tokenInfoRepository.Delete(tokenKey);
			return new ResultModel<bool>(HttpStatusCode.OK, num > 0);
		}

		private void AddLoginInfo(BaseAuthUser user)
		{
			var session = new UserSession()
			{
				UserId = user.Id,
				IP = HttpContextExtension.GetClientIPAddress(_httpContextAccessor.HttpContext.Request),
				LoginTime = DateTime.UtcNow,
			};

			_userSessionRepository.SetLogoutTimeByUser(user.Id, LogoutReasonEnum.LoggedInOnOtherDevice); // To handle do login on same time, mark the older one Log out
			_userSessionRepository.AddByDBContext(session);
		}

		public class AuthCondition
		{
			public string Prefix { get; set; }

			public string UserName { get; set; }

			public string SessionItemId { get; set; }
		}
	}
}
